const express = require('express');
const https = require('https');
const http = require('http');
const tls = require('tls');
const fs = require('fs');
const path = require('path');
const url = require('url');
const { execFile } = require('child_process');
const axios = require('axios'); // You may need to install this: npm install axios

require('dotenv').config({ path: path.join(__dirname, '.env') });

// Create a custom logging function
const logFile = path.join(__dirname, 'server_php_debug.log');

function logMessage(message, level = 'INFO') {
  const timestamp = new Date().toISOString();
  const logEntry = `[${timestamp}] [${level}] ${message}\n`;
  
  // Log to console
  console.log(logEntry);
  
  // Append to log file
  fs.appendFileSync(logFile, logEntry);
}

// Initialize the log file
logMessage('Server starting...', 'INFO');

const app = express();

// Parse ALLOWED_DOMAINS environment variable as an array
const allowedDomains = process.env.ALLOWED_DOMAINS ? process.env.ALLOWED_DOMAINS.split(',') : [];
logMessage(`Allowed domains: ${JSON.stringify(allowedDomains)}`, 'INFO');

// Middleware to parse JSON request bodies
app.use(express.json());

// Middleware to log all requests
app.use((req, res, next) => {
  logMessage(`${req.method} ${req.url}`, 'REQUEST');
  logMessage(`Headers: ${JSON.stringify(req.headers)}`, 'DEBUG');
  
  if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
    logMessage(`Body: ${JSON.stringify(req.body)}`, 'DEBUG');
  }
  
  // Track response
  const originalEnd = res.end;
  const originalWrite = res.write;
  const chunks = [];
  
  // Capture response data
  res.write = function(chunk) {
    chunks.push(Buffer.from(chunk));
    originalWrite.apply(res, arguments);
  };
  
  // Log completed response
  res.end = function(chunk) {
    if (chunk) {
      chunks.push(Buffer.from(chunk));
    }
    
    const body = Buffer.concat(chunks).toString('utf8');
    logMessage(`Response ${res.statusCode} - Length: ${body.length} bytes`, 'RESPONSE');
    
    // Log the first 500 characters of the response for debugging
    if (body.length > 0) {
      const preview = body.substring(0, 500) + (body.length > 500 ? '...' : '');
      logMessage(`Response preview: ${preview}`, 'DEBUG');
    }
    
    originalEnd.apply(res, arguments);
  };
  
  next();
});

// Helper function to get SSL options based on hostname
const getSSLOptions = (hostname) => {
  logMessage(`Getting SSL options for: ${hostname}`, 'INFO');
  
  const sslPrefix = process.env.SSL_PREFIX || '/etc/letsencrypt/live/';
  
  try {
    const keyPath = path.join(sslPrefix, hostname, 'privkey.pem');
    const certPath = path.join(sslPrefix, hostname, 'fullchain.pem');
    
    logMessage(`Attempting to read SSL files from: ${keyPath} and ${certPath}`, 'DEBUG');
    
    return {
      key: fs.readFileSync(keyPath),
      cert: fs.readFileSync(certPath),
    };
  } catch (error) {
    // Fallback to default certificates if domain-specific ones aren't found
    logMessage(`Could not load SSL certificates for ${hostname}, using defaults. Error: ${error.message}`, 'WARN');
    
    return {
      key: fs.readFileSync(process.env.SSL_KEY_FILE),
      cert: fs.readFileSync(process.env.SSL_CRT_FILE),
    };
  }
};

// Redirect HTTP to HTTPS
app.use((req, res, next) => {
  if (!req.secure && req.headers['x-forwarded-proto'] !== 'https') {
    const redirectUrl = `https://${req.headers.host}${req.url}`;
    logMessage(`Redirecting to HTTPS: ${redirectUrl}`, 'INFO');
    return res.redirect(redirectUrl);
  }
  next();
});

// NEW: Add proxy route for /api/v3/ to forward to the microservice
app.use('/api/v3', (req, res) => {
  const microserviceDomain = process.env.MICROSERVICE_DOMAIN || 'startersaas.xyz';
  const microservicePort = process.env.MICROSERVICE_PORT || 7000;
  const targetUrl = `http://${microserviceDomain}:${microservicePort}${req.url.replace(/^\/api\/v3/, '')}`;
  logMessage(`Proxying request to microservice: ${targetUrl}`, 'INFO');
  
  // Copy request headers but set proper host
  const headers = { ...req.headers };
  headers.host = `${microserviceDomain}:${microservicePort}`;
  
  // Prepare options for the proxy request
  const options = {
    method: req.method,
    url: targetUrl,
    headers: headers,
    responseType: 'arraybuffer'
  };
  
  // Add data for POST/PUT/PATCH requests
  if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
    options.data = req.body;
    logMessage(`Proxy request body: ${JSON.stringify(req.body)}`, 'DEBUG');
  }
  
  // Execute the proxy request
  axios(options)
    .then(response => {
      // Copy all response headers
      Object.entries(response.headers).forEach(([key, value]) => {
        res.setHeader(key, value);
      });
      
      // Send the response with the appropriate status
      res.status(response.status).send(response.data);
      logMessage(`Proxy request completed with status: ${response.status}`, 'INFO');
    })
    .catch(error => {
      logMessage(`Proxy error: ${error.message}`, 'ERROR');
      
      if (error.response) {
        // Forward error response from the target service
        Object.entries(error.response.headers).forEach(([key, value]) => {
          res.setHeader(key, value);
        });
        res.status(error.response.status).send(error.response.data);
      } else {
        // Generic error
        res.status(500).json({ error: 'Internal Server Error', message: error.message });
      }
    });
});

// Handle API requests without using wildcard routes
app.use((req, res, next) => {
  // Check if the path starts with '/api/v1/'
  if (req.path.startsWith('/api/v1/')) {
    // Extract the part after '/api/v1/'
    const apiPath = req.path.substring('/api/v1/'.length);
    
    if (!apiPath) {
      logMessage('API request received with no dynamic route', 'ERROR');
      return res.status(400).json({ error: 'Bad Request: No dynamic route found' });
    }

    logMessage(`Proxying API request: ${apiPath}`, 'INFO');
    
    const parsedUrl = url.parse(req.url);
    const proxyHost = process.env.API_PROXY_HOST || 'localhost';
    const proxyPort = process.env.API_PROXY_PORT || 3000;
    
    const requestOptions = {
      hostname: proxyHost,
      port: proxyPort,
      path: `/api/v1/${apiPath}${parsedUrl.search || ''}`,
      method: req.method,
      headers: { ...req.headers },
    };

    logMessage(`Proxy options: ${JSON.stringify(requestOptions)}`, 'DEBUG');
    
    let jsonPayload = null;
    if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
      jsonPayload = JSON.stringify(req.body);
      requestOptions.headers['Content-Type'] = 'application/json';
      requestOptions.headers['Content-Length'] = Buffer.byteLength(jsonPayload);
      
      logMessage(`Proxy payload: ${jsonPayload}`, 'DEBUG');
    }

    const proxyReq = http.request(requestOptions, (proxyRes) => {
      logMessage(`Proxy response received with status: ${proxyRes.statusCode}`, 'INFO');
      res.writeHead(proxyRes.statusCode, proxyRes.headers);
      proxyRes.pipe(res);
    });

    proxyReq.on('error', (error) => {
      logMessage(`Proxy error: ${error.message}`, 'ERROR');
      logMessage(`Proxy error stack: ${error.stack}`, 'ERROR');
      res.status(500).json({ error: 'Internal Server Error' });
    });

    if (jsonPayload) {
      proxyReq.write(jsonPayload);
    }

    proxyReq.end();
    return; // Stop further middleware execution
  }
  
  // If not an API request, continue to next middleware
  next();
});

// Function for direct PHP execution
const executePhpDirectly = (phpFilePath, domainPath, req, res) => {
  logMessage(`Executing PHP directly: ${phpFilePath}`, 'INFO');
  logMessage(`Request URI: ${req.url}`, 'INFO');
  logMessage(`Query string: ${url.parse(req.url).query || ''}`, 'INFO');
  
  // Create environment variables that PHP might expect
  const env = {
    ...process.env,
    DOCUMENT_ROOT: domainPath,
    SCRIPT_FILENAME: phpFilePath,
    REQUEST_URI: req.url,
    QUERY_STRING: url.parse(req.url).query || '',
    REQUEST_METHOD: req.method,
    HTTP_HOST: req.headers.host,
    REMOTE_ADDR: req.ip,
    SERVER_NAME: req.headers.host.split(':')[0],
    // Additional variables that PHP might expect
    GATEWAY_INTERFACE: 'CGI/1.1',
    SERVER_PROTOCOL: 'HTTP/1.1',
    // Pass HTTP headers as environment variables
    ...Object.entries(req.headers).reduce((env, [key, value]) => {
      env[`HTTP_${key.toUpperCase().replace(/-/g, '_')}`] = value;
      return env;
    }, {})
  };
  
  logMessage(`PHP environment: ${JSON.stringify(env)}`, 'DEBUG');
  
  // If this is a POST request, create a temporary file with the POST data
  if (req.method === 'POST' && req.body) {
    logMessage(`PHP execution with POST data: ${JSON.stringify(req.body)}`, 'INFO');
    
    // For application/json content type
    if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
      env.HTTP_CONTENT_TYPE = 'application/json';
      // Set raw POST data in environment
      env.RAW_POST_DATA = JSON.stringify(req.body);
    }
  }
  
  // Execute the PHP script
  const startTime = Date.now();
  logMessage(`Starting PHP execution at ${startTime}`, 'INFO');
  
  execFile('php', [phpFilePath], { env }, (error, stdout, stderr) => {
    const executionTime = Date.now() - startTime;
    logMessage(`PHP execution completed in ${executionTime}ms`, 'INFO');
    
    if (error) {
      logMessage(`PHP execution error: ${error.message}`, 'ERROR');
      logMessage(`PHP execution error stack: ${error.stack}`, 'ERROR');
      return res.status(500).send(`PHP Execution Error: ${error.message}`);
    }
    
    if (stderr) {
      logMessage(`PHP stderr output: ${stderr}`, 'WARN');
    }
    
    // Log stdout preview
    if (stdout) {
      const stdoutPreview = stdout.substring(0, 500) + (stdout.length > 500 ? '...' : '');
      logMessage(`PHP stdout preview: ${stdoutPreview}`, 'DEBUG');
    }
    
    // Set Content-Type to HTML by default, but look for Content-Type in PHP output
    let contentType = 'text/html; charset=utf-8';
    
    // Check if output contains headers
    const headerSeparator = stdout.indexOf('\r\n\r\n');
    if (headerSeparator > 0) {
      const headers = stdout.substring(0, headerSeparator);
      const body = stdout.substring(headerSeparator + 4);
      
      // Parse headers
      const headerLines = headers.split('\r\n');
      headerLines.forEach(line => {
        if (line.toLowerCase().startsWith('content-type:')) {
          contentType = line.substring('content-type:'.length).trim();
          logMessage(`Found Content-Type in PHP response: ${contentType}`, 'INFO');
        }
      });
      
      // Set response headers
      res.setHeader('Content-Type', contentType);
      
      // Send only the body
      res.send(body);
    } else {
      // No headers found, send entire output
      res.setHeader('Content-Type', contentType);
      res.send(stdout);
    }
  });
};

// Helper function to check for index files
const findIndexFile = (directoryPath) => {
  logMessage(`Finding index file in: ${directoryPath}`, 'INFO');
  
  // Common index file types to check for
  const indexFiles = ['index.html', 'index.htm', 'index.php'];
  
  for (const indexFile of indexFiles) {
    const filePath = path.join(directoryPath, indexFile);
    logMessage(`Checking for index file: ${filePath}`, 'DEBUG');
    
    if (fs.existsSync(filePath)) {
      logMessage(`Found index file: ${filePath}`, 'INFO');
      return { path: filePath, type: indexFile.endsWith('.php') ? 'php' : 'html' };
    }
  }
  
  logMessage(`No index file found in: ${directoryPath}`, 'INFO');
  return null;
};

// Handle PHP for all domains
app.use((req, res, next) => {
  const host = req.headers.host.split(':')[0]; // Remove port if present
  const domainPath = path.join('/var/www', host);
  
  logMessage(`Checking domain path: ${domainPath}`, 'INFO');
  
  // Make sure the domain directory exists
  if (!fs.existsSync(domainPath)) {
    logMessage(`Domain path does not exist: ${domainPath}`, 'INFO');
    return next();
  }
  
  // Handle direct PHP file requests
  if (req.path.endsWith('.php')) {
    const phpFilePath = path.join(domainPath, req.path);
    logMessage(`Direct PHP file request: ${phpFilePath}`, 'INFO');
    
    if (fs.existsSync(phpFilePath)) {
      logMessage(`PHP file exists: ${phpFilePath}`, 'INFO');
      return executePhpDirectly(phpFilePath, domainPath, req, res);
    } else {
      logMessage(`PHP file does not exist: ${phpFilePath}`, 'WARN');
    }
  }
  
  // Handle directory requests - check for index files
  if (req.path === '/' || req.path.endsWith('/')) {
    logMessage(`Directory request: ${req.path}`, 'INFO');
    
    const indexResult = findIndexFile(path.join(domainPath, req.path));
    
    if (indexResult) {
      if (indexResult.type === 'php') {
        logMessage(`Serving PHP index file: ${indexResult.path}`, 'INFO');
        return executePhpDirectly(indexResult.path, domainPath, req, res);
      } else {
        logMessage(`Serving HTML index file: ${indexResult.path}`, 'INFO');
        return res.sendFile(indexResult.path);
      }
    }
  }
  
  next();
});

// Serve static files from domain root first for all domains
app.use((req, res, next) => {
  const host = req.headers.host.split(':')[0];
  const domainPath = path.join('/var/www', host);
  
  if (!fs.existsSync(domainPath)) {
    return next();
  }
  
  logMessage(`Checking static file in domain root: ${path.join(domainPath, req.path)}`, 'DEBUG');
  express.static(domainPath)(req, res, (err) => {
    next();
  });
});

// Serve static files from build directory as fallback
app.use((req, res, next) => {
  const host = req.headers.host.split(':')[0];
  const domainPath = path.join('/var/www', host);
  const buildPath = path.join(domainPath, 'build');
  
  if (fs.existsSync(buildPath)) {
    logMessage(`Checking static file in build directory: ${path.join(buildPath, req.path)}`, 'DEBUG');
    express.static(buildPath)(req, res, next);
  } else {
    next();
  }
});

// Fall back to index.html without using catch-all route
app.use((req, res) => {
  const host = req.headers.host.split(':')[0];
  const domainPath = path.join('/var/www', host);
  
  logMessage(`Fallback handling for: ${req.path}`, 'INFO');
  
  // Check for index.html in domain root
  const rootIndexPath = path.join(domainPath, 'index.html');
  if (fs.existsSync(rootIndexPath)) {
    logMessage(`Serving root index.html: ${rootIndexPath}`, 'INFO');
    return res.sendFile(rootIndexPath);
  }
  
  // Check build folder
  const buildPath = path.join(domainPath, 'build');
  if (fs.existsSync(buildPath)) {
    const buildIndexPath = path.join(buildPath, 'index.html');
    if (fs.existsSync(buildIndexPath)) {
      logMessage(`Serving build index.html: ${buildIndexPath}`, 'INFO');
      return res.sendFile(buildIndexPath);
    }
  }
  
  // No matching file found
  logMessage(`No matching file found for: ${req.path}`, 'WARN');
  res.status(404).send('Not Found');
});

// Create HTTPS server with SNI support for multiple domains
const server = https.createServer({ 
  SNICallback: (hostname, cb) => {
    logMessage(`SNI callback for: ${hostname}`, 'INFO');
    const secureContext = tls.createSecureContext(getSSLOptions(hostname));
    if (cb) {
      cb(null, secureContext);
    } else {
      return secureContext;
    }
  }
}, app);

const httpsPort = process.env.HTTPS_PORT || 443;
const httpsInterface = process.env.SERVER_INTERFACE || '0.0.0.0';

server.listen(httpsPort, httpsInterface, () => {
  logMessage(`HTTPS server running on https://${httpsInterface}:${httpsPort}`, 'SUCCESS');
});

// Also create a HTTP server that redirects to HTTPS
const httpPort = process.env.HTTP_PORT || 80;
const httpInterface = process.env.SERVER_INTERFACE || '0.0.0.0';

http.createServer((req, res) => {
  logMessage(`HTTP request received: ${req.method} ${req.url}`, 'INFO');
  const redirectUrl = `https://${req.headers.host}${req.url}`;
  logMessage(`Redirecting HTTP to HTTPS: ${redirectUrl}`, 'INFO');
  res.writeHead(301, { Location: redirectUrl });
  res.end();
}).listen(httpPort, httpInterface, () => {
  logMessage(`HTTP to HTTPS redirect server running on port ${httpPort}`, 'SUCCESS');
});